package com.example.palette.util;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;

import androidx.core.content.ContextCompat;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.regex.Pattern;

public class LogUtil {

    private static final String TAG = "LogUtil";
    private static LogUtil INSTANCE;
    private static final String VERBOSE = " 追踪:";
    private static final String DEBUG = " 调试:";
    private static final String INFO = " 信息:";
    private static final String WARN = " 警告:";
    private static final String ERROR = " 异常:";
    private long mMaxFileSize = 100 * 1024 * 1024L;
    private int mSize = 7;
    private int mUnit = 86400000;
    private int mLevel = 1;
    private int mSameDayLogSuffix = 0;
    private boolean mIsNextDay;
    private boolean mCreateSuffix;
    private String mLogFileAbsolutePath;
    private String mCurrentFullDate;
    private Context mContext;
    private LogRecorder mLogRecorder;
    private Date mDate = new Date();
    private Set<String> mSet = new HashSet<>();
    private StringBuilder mStringBuilder = new StringBuilder();
    private SimpleDateFormat mYearFormat = new SimpleDateFormat("yyyy-");
    private SimpleDateFormat mSimpleFormat = new SimpleDateFormat("yyyy-MM-dd");
    private SimpleDateFormat mFullFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private Pattern mLogcatTimePattern = Pattern.compile("(^.*([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2}).*)");

    private LogUtil() {
        mSet.add(".java:");
        mSet.add("Error");
        mSet.add("error");
        mSet.add("Exception");
        mSet.add("exception");
        mSet.add("Google");
        mSet.add("google");
        mSet.add("native: #");
        mSet.add("art/runtime");
        mSet.add(" pc ");
        mSet.add("SIGABRT");
        mSet.add("SIGBUS");
        mSet.add("SIGFPE");
        mSet.add("SIGILL");
        mSet.add("SIGSEGV");
        mSet.add("SIGTRAP");
        mSet.add("SIGSYS");
        mSet.add("SIGSTKFLT");
    }

    public static LogUtil getInstance() {
        if (INSTANCE == null) {
            synchronized (LogUtil.class) {
                if (INSTANCE == null) {
                    INSTANCE = new LogUtil();
                }
            }
        }
        return INSTANCE;
    }

    public void start(Context context) {
        if (context == null) {
            throw new RuntimeException("context can not be null");
        }
        mContext = context.getApplicationContext();
        generateLogFileDir();
        end();
        mLogRecorder = new LogRecorder(String.valueOf(android.os.Process.myPid()), mLogFileAbsolutePath, mContext.getPackageName(), mLevel);
        mLogRecorder.start();
    }

    public void end() {
        if (mLogRecorder != null) {
            Log.e(TAG, VERBOSE + ">>>>> Prepare end record log <<<<<");
            mLogRecorder.stopRecord();
            mLogRecorder = null;
        }
    }

    public static void logv(String msg) {
        Log.v(TAG, VERBOSE + msg);
    }

    public static void logd(String msg) {
        Log.d(TAG, DEBUG + msg);
    }

    public static void logi(String msg) {
        Log.i(TAG, INFO + msg);
    }

    public static void logw(String msg) {
        Log.w(TAG, WARN + msg);
    }

    public static void loge(String msg) {
        Log.e(TAG, ERROR + msg);
    }

    public static void logStackTrace(Throwable e, String... info) {
        if (e != null) {
            StackTraceElement[] stackTrace = e.getStackTrace();
            Throwable[] suppressed = e.getSuppressed();
            Throwable cause = e.getCause();
            loge(">>>>> 开始 <<<<<");
            if (info != null && info.length > 0) {
                StringBuilder builder = new StringBuilder();
                for (String s : info) {
                    builder.append(s);
                }
                loge(builder.toString());
            }
            loge(e.toString());
            for (StackTraceElement traceElement : stackTrace) {
                loge("\tat " + traceElement);
            }
            for (Throwable t : suppressed) {
                loge("\tat " + t.toString());
            }
            if (cause != null) {
                loge("\tat " + cause);
            }
            loge(">>>>> 结束 <<<<<");
        }
    }

    public String getLogFileDir() {
        return mLogFileAbsolutePath;
    }

    public LogUtil setLogLifeCycle(int lifeCycleOfDay) {
        mSize = lifeCycleOfDay;
        return INSTANCE;
    }

    public LogUtil setLogMaxSize(int maxSizeOfMB) {
        mMaxFileSize = maxSizeOfMB * 1024 * 1024L;
        return INSTANCE;
    }

    public LogUtil setLogLevel(int filterLevel) {
        mLevel = filterLevel;
        return INSTANCE;
    }

    public LogUtil addLogKeyword(String keyword) {
        if (!TextUtils.isEmpty(keyword)) {
            mSet.add(keyword);
        }
        return INSTANCE;
    }

    private void generateLogFileDir() {
        File file;
        String[] splits = mContext.getPackageName().split("\\.");
        String logFileRelativePath = splits[splits.length - 1] + File.separator + "log";
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
                            && ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                        file = new File(Environment.getExternalStorageDirectory(), logFileRelativePath);
                    } else {
                        file = new File(mContext.getExternalCacheDir(), logFileRelativePath);
                    }
                } else {
                    file = new File(Environment.getExternalStorageDirectory(), logFileRelativePath);
                }
            } else {
                file = new File(mContext.getExternalCacheDir(), logFileRelativePath);
            }
        } else {
            file = new File(mContext.getCacheDir(), logFileRelativePath);
        }
        if (!file.exists()) {
            file.mkdirs();
        } else {
            autoClearExpireLog(file);
        }
        mLogFileAbsolutePath = file.getAbsolutePath();
    }

    private void autoClearExpireLog(File file) {
        try {
            File[] files = file.listFiles();
            String days = mSimpleFormat.format(new Date(new Date().getTime() + (-mSize * mUnit)));
            if (files != null) {
                for (File f : files) {
                    String name = f.getName();
                    if (name.length() > 10) {
                        name = name.substring(0, 10);
                    }
                    if (name.compareTo(days) < 0) {
                        f.delete();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String getYear() {
        mDate.setTime(System.currentTimeMillis());
        return mYearFormat.format(mDate);
    }

    private class LogRecorder extends Thread {

        private String mPID;
        private File mFile;
        private OutputStream mFos;
        private String mCmds;
        private String mPackageName;
        private Process mProcess;
        private BufferedReader mBufferedReader;
        private boolean mRunning = true;

        public LogRecorder(String pid, String path, String pkgName, int level) {
            try {
                mIsNextDay = false;
                mPID = pid;
                mPackageName = pkgName;
                mDate.setTime(System.currentTimeMillis());
                mCurrentFullDate = mFullFormat.format(mDate);
                String name;
                if (mCreateSuffix) {
                    mSameDayLogSuffix++;
                    name = mSimpleFormat.format(mDate) + "-" + mSameDayLogSuffix + ".log";
                } else {
                    name = mSimpleFormat.format(mDate) + ".log";
                }
                mCreateSuffix = false;
                mFile = new File(path, name);
                if (!mFile.exists()) {
                    mFile.createNewFile();
                }
                mFos = new FileOutputStream(mFile, true);
                if (level == 1) {
                    mCmds = "logcat *:v | grep \"(" + mPID + ")\"";
                } else if (level == 2) {
                    mCmds = "logcat *:d | grep \"(" + mPID + ")\"";
                } else if (level == 3) {
                    mCmds = "logcat *:i | grep \"(" + mPID + ")\"";
                } else if (level == 4) {
                    mCmds = "logcat *:w | grep \"(" + mPID + ")\"";
                } else if (level == 5) {
                    mCmds = "logcat *:e | grep \"(" + mPID + ")\"";
                } else {
                    mCmds = "logcat *:v | grep \"(" + mPID + ")\"";
                }
                Log.e(TAG, VERBOSE + ">>>>> Prepare start record log , Save directory is : " + getLogFileDir() + " , FileName is : " + name + " <<<<<");
            } catch (Exception e) {
                mCurrentFullDate = null;
                mFos = null;
                mFile = null;
                e.printStackTrace();
            }
        }

        private void stopRecord() {
            mRunning = false;
        }

        private boolean matchKeyword(String text) {
            Iterator<String> iterator = mSet.iterator();
            while (iterator.hasNext()) {
                String next = iterator.next();
                if (text.contains(next)) {
                    return true;
                }
            }
            return false;
        }

        private byte[] fillYear(String content) {
            if (mStringBuilder != null) {
                mStringBuilder.setLength(0);
                mStringBuilder.append(getYear()).append(content).append("\n");
                String string = mStringBuilder.toString();
                return string.getBytes();
            } else {
                return new byte[0];
            }
        }

        @Override
        public void run() {
            try {
                mProcess = Runtime.getRuntime().exec(mCmds);
                mBufferedReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()), 1024);
                while (mRunning) {
                    String line = mBufferedReader.readLine();
                    if (line == null || line.isEmpty()) {
                        continue;
                    }
                    if (mFile == null || !mFile.exists() || mCurrentFullDate == null || mFos == null || mFile.length() >= mMaxFileSize) {
                        mCreateSuffix = true;
                        break;
                    }
                    if (mLogcatTimePattern.matcher(line).matches()) {
                        if (mSimpleFormat.parse(getYear() + line.substring(0, 18)).after(mSimpleFormat.parse(mCurrentFullDate))) {
                            mIsNextDay = true;
                            mCreateSuffix = false;
                            mSameDayLogSuffix = 0;
                            break;
                        }
                        if (mFullFormat.parse(getYear() + line.substring(0, 18)).before(mFullFormat.parse(mCurrentFullDate))) {
                            continue;
                        }
                        if ((line.contains(mPID) && (line.contains(TAG) || line.contains(mPackageName))) || line.contains(mPackageName) || matchKeyword(line)) {
                            mFos.write(fillYear(line));
                        }
                    }
                }
            } catch (Exception e) {
                if (mFile == null || !mFile.exists()) {
                    mCreateSuffix = true;
                }
                e.printStackTrace();
            } finally {
                if (mProcess != null) {
                    mProcess.destroy();
                    mProcess = null;
                }
                if (mBufferedReader != null) {
                    try {
                        mBufferedReader.close();
                        mBufferedReader = null;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (mFos != null) {
                    try {
                        mFos.close();
                        mFos = null;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (mIsNextDay || mCreateSuffix) {
                    LogUtil.this.start(mContext);
                } else {
                    mSameDayLogSuffix = 0;
                }
            }
        }
    }
}
